﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TextTemplating;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using Microsoft.Data.Schema.SchemaModel;

namespace TFDP.Common.T4
{
    [Serializable]
    public sealed class TemplateHost : ITextTemplatingEngineHost, ITextTemplatingSessionHost
    {
        #region Members
        private string templateFile;
        private string fileExtension;
        private Encoding fileEncoding;
        private CompilerErrorCollection errors;
        #endregion

        public CompilerErrorCollection Errors
        {
            get { return errors; }
        }

        public DataSchemaModel Model
        {
            get;
            private set;
        }

        public IModelElement Element
        {
            get;
            private set;
        }

        internal void SetSchemaModel(DataSchemaModel model)
        {
            this.Model = model;
        }

        internal void SetElement(IModelElement element)
        {
            this.Element = element;
        }

        internal void Initialize()
        {
            if (this.artifacts == null)
            {
                this.artifacts = new List<GeneratedArtifact>();
            }

            this.artifacts.Clear();
        }

        #region ITextTemplatingEngineHost Members

        public object GetHostOption(string optionName)
        {
            object retval;
            switch (optionName)
            {
                case "CacheAssemblies":
                    retval = true;
                    break;
                default:
                    retval = null;
                    break;
            }

            return retval;
        }

        public bool LoadIncludeText(string requestFileName, out string content, out string location)
        {
            content = String.Empty;
            location = String.Empty;

            if (!Path.IsPathRooted(requestFileName))
            {
                requestFileName = Path.Combine(Path.GetDirectoryName(TemplateFile), requestFileName);
            }
            if (File.Exists(requestFileName))
            {
                content = File.ReadAllText(requestFileName);
                return true;
            }
            else
            {
                /// TODO: Implement include path resolving
                return false;
            }
        }

        public void LogErrors(System.CodeDom.Compiler.CompilerErrorCollection errors)
        {
            this.errors = errors;
        }

        public AppDomain ProvideTemplatingAppDomain(string content)
        {
            return AppDomain.CreateDomain("Generation App Domain");
        }

        public string ResolveAssemblyReference(string assemblyReference)
        {
            if (File.Exists(assemblyReference))
                return assemblyReference;

            string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), assemblyReference);

            if (File.Exists(candidate))
                return candidate;

            try
            {
                Assembly asm = Assembly.LoadWithPartialName(assemblyReference);
                if (asm == null)
                    return String.Empty;

                return asm.Location;
            }
            catch (Exception)
            {
                return String.Empty;
            }
        }

        public Type ResolveDirectiveProcessor(string processorName)
        {
            throw new Exception("Directive Processor not found");
        }

        public string ResolveParameterValue(string directiveId, string processorName, string parameterName)
        {
            if (directiveId == null)
                throw new ArgumentNullException("directiveId");

            if (processorName == null)
                throw new ArgumentNullException("processorName");

            if (parameterName == null)
                throw new ArgumentNullException("parameterName");

            return String.Empty;
        }

        public string ResolvePath(string path)
        {
            if (path == null)
                throw new ArgumentNullException("path");

            if (File.Exists(path))
                return path;

            //Maybe the file is in the same folder as the text template that 
            //called the directive.
            //----------------------------------------------------------------
            string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), path);
            if (File.Exists(candidate))
                return candidate;

            //Look more places.
            //----------------------------------------------------------------
            //More code can go here...
            //If we cannot do better, return the original file name.
            return path;
        }

        public void SetFileExtension(string extension)
        {
            this.fileExtension = extension;
        }

        public void SetOutputEncoding(Encoding encoding, bool fromOutputDirective)
        {
            this.fileEncoding = encoding;
        }

        public IList<string> StandardAssemblyReferences
        {
            get
            {
                return new string[]
                {
                    typeof(System.Uri).Assembly.Location
                };
            }
        }

        public IList<string> StandardImports
        {
            get
            {
                return new string[]
                { 
                    "System"
                };
            }
        }

        public string TemplateFile
        {
            get { return templateFile; }
            set { templateFile = value; }
        }

        #endregion

        #region ITextTemplatingSessionHost Members
        private ITextTemplatingSession session;

        public ITextTemplatingSession CreateSession()
        {
            return new TextTemplatingSession();
        }

        public ITextTemplatingSession Session
        {
            get
            {
                return this.session;
            }
            set
            {
                this.session = value;
            }
        }
        #endregion

        #region Artifact Handling
        private List<GeneratedArtifact> artifacts;

        public IEnumerable<GeneratedArtifact> Artifacts
        {
            get { return artifacts.AsReadOnly(); }
        }

        public void AddArtifact(GeneratedArtifact artifact)
        {
            if (artifact == null)
            {
                throw new ArgumentNullException("artifact");
            }

            artifacts.Add(artifact);
        }
        #endregion
    }
}
